# +----------------------------------------------------------------------
# | ChatWork智能聊天办公系统
# +----------------------------------------------------------------------
# | 软件声明: 本系统并非自由软件,未经授权任何形式的商业使用均属非法。
# | 版权保护: 任何企业和个人不允许对程序代码以任何形式任何目的复制/分发。
# | 授权要求: 如有商业使用需求,请务必先与版权所有者取得联系并获得正式授权。
# +----------------------------------------------------------------------
# | Author: ChatWork Team <2474369941@qq.com>
# +----------------------------------------------------------------------
import json
import time
from typing import List
from decimal import Decimal
from pydantic import TypeAdapter
from tortoise.models import in_transaction
from exception import AppException
from common.utils.times import TimeUtil
from common.utils.tools import ToolsUtil
from common.utils.config import ConfigUtil
from common.enums.pay import PayEnum
from common.enums.client import ClientEnum
from common.enums.subscribe import SubscribeEnum
from common.models.users import UserModel
from common.models.market import SubscribeOrderModel
from common.models.market import SubscribePackageModel
from common.models.market import SubscribePricingModel
from common.models.market import SubscribePrivilegeModel
from common.models.market import SubscribeSpecificsModel
from common.service.subscribe_logic import SubscribeLogic
from apps.admin.schemas.market import subscribe_schema as schema
from apps.api.service.payNotify_service import PayNotifyService


class SubscribeService:

    @classmethod
    async def pricing(cls, user_id: int, calendar: str) -> List[schema.SubscribePricingVo]:
        """
        订阅套餐定价表

        Args:
            user_id (int): 用户ID
            calendar (str): 时间类型

        Returns:
            List[schema.SubscribePricingVo]

        Author:
            zero
        """
        # 时间选项
        if calendar not in ["day", "month", "year", "perpetual"]:
            raise AppException("calendar 参数异常!")

        # 查询定价
        pricing_free = await SubscribePricingModel.filter(is_builtin=1).limit(1).all()
        pricing_list = pricing_free + await (SubscribePricingModel
                                             .filter(calendar=calendar)
                                             .filter(is_disable=0)
                                             .filter(is_delete=0)
                                             .filter(is_builtin=int(0))
                                             .order_by("-is_builtin", "sort", "id")
                                             .group_by("package_id")
                                             .all())

        # 查询套餐
        package_ids = list(set([item.package_id for item in pricing_list]))
        packages = await (SubscribePackageModel
                          .filter(id__in=package_ids)
                          .filter(is_delete=0)
                          .filter(is_disable=int(0))
                          .order_by("-is_builtin", "-sort")
                          .all().values("id", "name", "description", "is_builtin", "is_disable"))
        package_dict = {item["id"]: item for item in packages}

        # 套餐列表
        _lists = []
        for item in pricing_list:
            package = package_dict.get(item.package_id, None)
            if package:
                _lists.append(schema.SubscribePricingVo(
                    id=int(package["id"]),
                    pricing_id=item.id,
                    name=package["name"],
                    tags=item.tags,
                    description=package["description"],
                    sell_price=item.sell_price,
                    line_price=item.line_price,
                    my_version=0,
                    is_purchase=1,
                    is_builtin=package["is_builtin"],
                    is_disable=package["is_disable"],
                ))

        # 从新排序
        index_m = {pid: idx for idx, pid in enumerate(package_ids)}
        sorted_list = sorted(_lists, key=lambda member: index_m[member.id])

        # 处理状态
        userMember = await SubscribeLogic.get_user_vip(user_id=user_id)
        for item in sorted_list:
            if item.is_builtin and item.id == 1:
                item.is_purchase = 0
            if item.id == userMember["id"]:
                item.my_version = int(1)
                break
            item.is_purchase = 0

        # 返回结果
        return _lists

    @classmethod
    async def lists(cls) -> List[schema.SubscribePackageListVo]:
        """
        订阅套餐列表

        Returns:
            List[schema.SubscribePackageListVo]: 充值套餐列表Vo。

        Author:
            zero
        """
        lists = await (SubscribePackageModel
                       .filter(is_delete=0)
                       .order_by("-is_builtin", "sort")
                       .all())

        _lists = []
        for item in lists:
            _lists.append(schema.SubscribePackageListVo(
                id=item.id,
                name=item.name,
                description=item.description,
                train_num=item.train_num,
                robot_num=item.robot_num,
                know_num=item.know_num,
                mcp_num=item.mcp_num,
                min_price=item.min_price,
                max_price=item.max_price,
                sort=item.sort,
                is_builtin=item.is_builtin,
                is_disable=item.is_disable,
                create_time=TimeUtil.timestamp_to_date(item.create_time),
                update_time=TimeUtil.timestamp_to_date(item.update_time)
            ))

        return _lists

    @classmethod
    async def detail(cls, id_: int):
        # 查询套餐信息
        package = await SubscribePackageModel.filter(id=id_).get()

        # 权益描述列表
        specific_list = []
        specifics = await SubscribeSpecificsModel.filter(package_id=package.id).order_by("sort").all()
        for specific in specifics:
            specific_list.append(TypeAdapter(schema.SpecificsVo).validate_python(specific.__dict__))

        # 查定价列表
        pricing_list = []
        pricing = await SubscribePricingModel.filter(package_id=package.id).order_by("sort", "-id").all()
        for item in pricing:
            pricing_list.append(TypeAdapter(schema.PricingVo).validate_python(item.__dict__))

        # 订阅限制数据
        delete_ids = []
        apply_list = []
        privileges = await SubscribePrivilegeModel.filter(package_id=package.id).all().values()
        for privilege in privileges:
            name = SubscribeEnum.get_apply_lists(privilege["marks"])
            if not name:
                delete_ids.append(privilege["id"])
            else:
                privilege["name"] = name
                privilege["logo"] = ""
                apply_list.append(TypeAdapter(schema.PrivilegeVo).validate_python(privilege))

        # 处理已被删的模型
        if delete_ids:
            await SubscribePrivilegeModel.filter(id__in=delete_ids).delete()

        # 处理补全缺少应用
        apply_marts: List[str] = [str(item.marks) for item in apply_list]
        for key, name in SubscribeEnum.get_apply_lists().items():
            if key not in apply_marts:
                insert_data = {
                    "package_id": package.id,
                    "scene": key,
                    "marks": key,
                    "daily_limit": 0,
                    "is_disable": 1,
                    "create_time": int(time.time()),
                    "update_time": int(time.time()),
                }
                ins = await SubscribePrivilegeModel.create(**insert_data)
                insert_data["id"] = ins.id
                insert_data["name"] = name
                insert_data["logo"] = ""
                apply_list.append(TypeAdapter(schema.PrivilegeVo).validate_python(insert_data))

        return schema.SubscribeDetailVo(
            id=package.id,
            name=package.name,
            description=package.description,
            robot_num=package.robot_num,
            know_num=package.know_num,
            train_num=package.train_num,
            mcp_num=package.mcp_num,
            sort=package.sort,
            is_builtin=package.is_builtin,
            is_disable=package.is_disable,
            pricing_list=pricing_list,
            specific_list=specific_list,
            apply_list=apply_list
        )

    @classmethod
    async def models(cls):
        apply_list = []
        for key, name in SubscribeEnum.get_apply_lists().items():
            apply_list.append(schema.PrivilegeVo(
                id=0,
                logo="",
                name=name,
                scene=key,
                marks=key,
                daily_limit=0,
                is_disable=0
            ))

        return {
            "apply_list": apply_list
        }

    @classmethod
    async def add(cls, post: schema.SubscribeAddIn):
        if not post.pricing_list:
            raise AppException("至少需要添加1个定价")

        # 所有价格
        prices = [item.sell_price for item in post.pricing_list]

        # 事务处理
        async with in_transaction("mysql"):
            # 添加套餐
            package = await SubscribePackageModel.create(
                name=post.name,
                description=post.description,
                train_num=post.train_num,
                robot_num=post.robot_num,
                know_num=post.know_num,
                mcp_num=post.mcp_num,
                min_price=min(prices),
                max_price=max(prices),
                sort=post.sort,
                is_disable=post.is_disable,
                create_time=int(time.time()),
                update_time=int(time.time())
            )

            # 定价
            pricing_list = []
            for item in post.pricing_list:
                p = item.__dict__
                p["package_id"] = package.id
                p["create_time"] = int(time.time())
                p["update_time"] = int(time.time())
                if p.get("id"):
                    del p["id"]
                pricing_list.append(SubscribePricingModel(**p))

            # 权益
            specifics_list = []
            for item in post.specific_list:
                p = item.__dict__
                p["package_id"] = int(package.id)
                p["create_time"] = int(time.time())
                p["update_time"] = int(time.time())
                if p.get("id"):
                    del p["id"]
                specifics_list.append(SubscribeSpecificsModel(**p))

            # 限制
            privilege_list = []
            for item in post.apply_list:
                p = item.__dict__
                p["package_id"] = package.id
                p["create_time"] = int(time.time())
                p["update_time"] = int(time.time())
                if p.get("id"):
                    del p["id"]
                privilege_list.append(SubscribePrivilegeModel(**p))

            # 批量保存
            await SubscribePricingModel.bulk_create(pricing_list)
            await SubscribeSpecificsModel.bulk_create(specifics_list)
            await SubscribePrivilegeModel.bulk_create(privilege_list)

    @classmethod
    async def edit(cls, post: schema.SubscribeEditIn):
        if not post.pricing_list:
            raise AppException("至少需要添加1个定价")

        # 所有价格
        prices = [item.sell_price for item in post.pricing_list]

        # 查询套餐
        package = await SubscribePackageModel.filter(id=post.id, is_delete=0).get()

        # 事务处理
        async with in_transaction("mysql"):
            # 更新套餐
            await SubscribePackageModel.filter(id=post.id).update(
                name=post.name,
                description=post.description,
                train_num=post.train_num,
                robot_num=post.robot_num,
                know_num=post.know_num,
                mcp_num=post.mcp_num,
                min_price=min(prices),
                max_price=max(prices),
                sort=post.sort,
                is_disable=post.is_disable,
                update_time=int(time.time())
            )

            # 权益删除
            sqls_spec_ids = await SubscribeSpecificsModel.filter(package_id=post.id).values("id")
            sqls_spec_ids = set([item["id"] for item in sqls_spec_ids])
            post_spec_ids = set([item.id for item in post.specific_list])
            delete_spec_ids = sqls_spec_ids - post_spec_ids
            if delete_spec_ids:
                await SubscribeSpecificsModel.filter(id__in=list(delete_spec_ids)).delete()

            # 权益编辑
            for item in post.specific_list:
                if not item.id:
                    p = item.__dict__
                    del p["id"]
                    p["package_id"] = int(post.id)
                    p["create_time"] = int(time.time())
                    p["update_time"] = int(time.time())
                    await SubscribeSpecificsModel.create(**p)
                else:
                    p = item.__dict__
                    d = p["id"]
                    del p["id"]
                    p["update_time"] = int(time.time())
                    await SubscribeSpecificsModel.filter(id=d).update(**p)

            # 限制编辑
            for item in post.apply_list:
                await SubscribePrivilegeModel.filter(id=item.id).update(
                    daily_limit=item.daily_limit,
                    is_disable=item.is_disable,
                    update_time=int(time.time())
                )

            #  定价编辑
            if not package.is_builtin:
                # 定价IDS
                sqls_pricing_ids = await SubscribePricingModel.filter(package_id=post.id).values("id")
                sqls_pricing_ids = set([item["id"] for item in sqls_pricing_ids])
                post_pricing_ids = set([item.id for item in post.pricing_list])

                # 定价需删除
                delete_pricing_ids = sqls_pricing_ids - post_pricing_ids
                if delete_pricing_ids:
                    await SubscribePricingModel.filter(id__in=delete_pricing_ids).delete()

                # 定价需更新
                pricing_insert = []
                for item in post.pricing_list:
                    if not item.id:
                        p = item.__dict__
                        del p["id"]
                        p["package_id"] = post.id
                        p["create_time"] = int(time.time())
                        p["update_time"] = int(time.time())
                        pricing_insert.append(SubscribePricingModel(**p))
                    else:
                        _id = item.id
                        p = item.__dict__
                        del p["id"]
                        p["update_time"] = int(time.time())
                        await SubscribePricingModel.filter(id=_id).update(**p)

                # 更新到数据库
                await SubscribePricingModel.bulk_create(pricing_insert)
            else:
                # 系统内置定价处理 (部份内容不允许更新)
                price = post.pricing_list[0]
                await SubscribePricingModel.filter(id=price.id).update(
                    tags=price.tags,
                    give_points=price.give_points,
                    line_price=price.line_price,
                    sell_price=Decimal(0),
                    duration=0,
                    sort=0,
                    is_builtin=1,
                    is_disable=0
                )

    @classmethod
    async def delete(cls, id_: int):
        package = await SubscribePackageModel.filter(id=id_).get()
        if package.is_builtin:
            raise AppException("系统内置套餐不允许被删除")

        await SubscribePackageModel.filter(id=id_).update(
            is_delete=1,
            delete_time=int(time.time())
        )

    @classmethod
    async def get_config(cls) -> schema.SubscribeConfigIn:
        """
        订阅配置读取

        Returns:
            schema.SubscribeConfigIn: 订阅配置Vo。

        Author:
            zero
        """
        status = await ConfigUtil.get("subscribe", "status", "0")
        lines = await ConfigUtil.get("subscribe", "timeline") or {}
        return schema.SubscribeConfigIn(status=int(status), timeline={
            "day": lines.get("day", False),
            "month": lines.get("month", False),
            "year": lines.get("year", False),
            "perpetual": lines.get("perpetual", False)
        })

    @classmethod
    async def set_config(cls, post: schema.SubscribeConfigIn):
        """
        订阅配置修改

        Args:
            post (schema.SubscribeConfigIn): 订阅配置参数。

        Author:
            zero
        """
        timeline = {
            "day": post.timeline.get("day", False),
            "month": post.timeline.get("month", False),
            "year": post.timeline.get("year", False),
            "perpetual": post.timeline.get("perpetual", False)
        }

        await ConfigUtil.set("subscribe", "status", post.status)
        await ConfigUtil.set("subscribe", "timeline", timeline)

    @classmethod
    async def open_vip(cls, admin_id: int, params: schema.SubscribeOpenIn):
        # 查询用户
        user = await UserModel.filter(id=params.user_id, is_delete=0).first()
        if not user:
            raise AppException("用户异常!")

        # 查询定价
        pricing = await SubscribePricingModel.filter(id=params.pricing_id, is_delete=0).first()
        if not pricing:
            raise AppException("套餐不存在!")
        if pricing.is_disable:
            raise AppException("套餐已下架!")

        # 查询套餐
        package = await SubscribePackageModel.filter(id=pricing.package_id, is_delete=0).first()
        if not package:
            raise AppException("会员套餐不存在!")
        if package.is_disable:
            raise AppException("会员套餐已下架!")

        # 验证套餐
        userMember = await SubscribeLogic.get_user_vip(user_id=params.user_id)
        if userMember:
            # 排除比自己套餐低的套餐
            packageIds = await SubscribePackageModel.filter(is_delete=0).order_by(*["sort", "id"]).values("id")
            packageIds = [item["id"] for item in packageIds]
            index = 0
            for i in packageIds:
                if i == userMember["id"]:
                    break
                else:
                    index += 1

            packageIds = packageIds[index:]

            # 验证套餐是否可进行开通
            if pricing.package_id not in packageIds:
                raise AppException("抱歉,您无法订阅比当前套餐低的套餐!")

        # 创建订单
        order = await SubscribeOrderModel.create(
            user_id=params.user_id,
            admin_id=admin_id,
            terminal=ClientEnum.PC,
            order_sn=await ToolsUtil.make_order_sn(SubscribeOrderModel, "order_sn"),
            pay_way=PayEnum.WAY_BALANCE,
            package_id=package.id,
            pricing_id=pricing.id,
            source_client=2,
            order_amount=pricing.sell_price,
            paid_amount=params.paid_amount,
            create_time=int(time.time()),
            update_time=int(time.time()),
            snapshot=json.dumps({
                "name": package.name,
                "description": package.description,
                "train_num": package.train_num,
                "robot_num": package.robot_num,
                "know_num": package.know_num,
                "sort": package.sort,
                "tags": pricing.tags,
                "calendar": pricing.calendar,
                "duration": pricing.duration,
                "give_points": int(pricing.give_points),
                "sell_price": str(pricing.sell_price),
                "line_price": str(pricing.line_price)
            }, ensure_ascii=False)
        )

        # 回调开通
        await PayNotifyService.subscribe(order_sn=order.order_sn)
